/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.support.definition.export;

import com.eviware.soapui.impl.rest.RestService;
import com.eviware.soapui.impl.support.definition.InterfaceDefinition;
import com.eviware.soapui.impl.support.definition.InterfaceDefinitionPart;
import com.eviware.soapui.impl.wsdl.support.Constants;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.XmlObject;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractDefinitionExporter<T extends Interface> implements DefinitionExporter {
    private InterfaceDefinition<T> definition;

    public AbstractDefinitionExporter(InterfaceDefinition<T> definition) {
        this.definition = definition;
    }

    public InterfaceDefinition<T> getDefinition() {
        return definition;
    }

    public void setDefinition(InterfaceDefinition<T> definition) {
        this.definition = definition;
    }

    public String export(String folderName) throws Exception {
        if (definition.getDefinitionCache() == null || !definition.getDefinitionCache().validate()) {
            throw new Exception("Definition is not cached for export");
        }

        File outFolder = new File(folderName);
        if (!outFolder.exists() && !outFolder.mkdirs()) {
            throw new Exception("Failed to create directory [" + folderName + "]");
        }

        Map<String, String> urlToFileMap = new HashMap<String, String>();

        setFilenameForPart(definition.getDefinitionCache().getRootPart(), urlToFileMap, null);

        List<InterfaceDefinitionPart> partList = definition.getDefinitionCache().getDefinitionParts();
        for (InterfaceDefinitionPart part : partList) {
            setFilenameForPart(part, urlToFileMap, null);
        }

        for (InterfaceDefinitionPart part : partList) {
            // XmlObject obj = XmlObject.Factory.parse( part.getContent() );
            XmlObject obj = XmlUtils.createXmlObject(part.getContent());
            replaceImportsAndIncludes(obj, urlToFileMap, part.getUrl());
            postProcessing(obj, part);
            obj.save(new File(outFolder, urlToFileMap.get(part.getUrl())));
        }

        return folderName + File.separatorChar
                + urlToFileMap.get(definition.getDefinitionCache().getRootPart().getUrl());
    }

    public StringToStringMap createFilesForExport(String urlPrefix) throws Exception {
        StringToStringMap result = new StringToStringMap();
        Map<String, String> urlToFileMap = new HashMap<String, String>();

        if (urlPrefix == null) {
            urlPrefix = "";
        }

        setFilenameForPart(definition.getDefinitionCache().getRootPart(), urlToFileMap, urlPrefix);

        List<InterfaceDefinitionPart> partList = definition.getDefinitionCache().getDefinitionParts();
        for (InterfaceDefinitionPart part : partList) {
            if (!part.isRootPart()) {
                setFilenameForPart(part, urlToFileMap, urlPrefix);
            }
        }

        for (InterfaceDefinitionPart part : partList) {
            // XmlObject obj = XmlObject.Factory.parse( part.getContent() );
            XmlObject obj = XmlUtils.createXmlObject(part.getContent());
            replaceImportsAndIncludes(obj, urlToFileMap, part.getUrl());
            String urlString = urlToFileMap.get(part.getUrl());
            if (urlString.startsWith(urlPrefix)) {
                urlString = urlString.substring(urlPrefix.length());
            }

            result.put(urlString, obj.xmlText());

            if (part.isRootPart()) {
                result.put("#root#", urlString);
            }
        }

        return result;
    }

    protected void postProcessing(XmlObject obj, InterfaceDefinitionPart part) {
    }

    private void setFilenameForPart(InterfaceDefinitionPart part, Map<String, String> urlToFileMap, String urlPrefix)
            throws MalformedURLException {

        String path = part.getUrl();

        try {
            URL url = new URL(path);
            path = url.getPath();
        } catch (MalformedURLException ignored) {
        }

        int ix = path.lastIndexOf('/');
        if (ix == -1) {
            ix = path.lastIndexOf('\\');
        }
        String fileName = ix == -1 ? path : path.substring(ix + 1);

        ix = fileName.lastIndexOf('.');
        if (ix != -1) {
            fileName = fileName.substring(0, ix);
        }

        String type = part.getType();

        if (type.equals(Constants.WSDL11_NS)) {
            fileName += ".wsdl";
        } else if (part.getType().equals(Constants.XSD_NS)) {
            fileName += ".xsd";
        } else if (getDefinition().getInterface() instanceof RestService
                && part.getType().equals(((RestService) getDefinition().getInterface()).getWadlVersion())) {
            fileName += ".wadl";
        } else {
            fileName += ".xml";
        }

        if (urlPrefix != null) {
            fileName = urlPrefix + fileName;
        }

        int cnt = 1;
        while (urlToFileMap.containsValue(fileName)) {
            ix = fileName.lastIndexOf('.');
            fileName = fileName.substring(0, ix) + "_" + cnt + fileName.substring(ix);
            cnt++;
        }

        urlToFileMap.put(part.getUrl(), fileName);
    }

    private void replaceImportsAndIncludes(XmlObject xmlObject, Map<String, String> urlToFileMap, String baseUrl)
            throws Exception {
        String[] paths = getLocationXPathsToReplace();

        for (String path : paths) {
            XmlObject[] locations = xmlObject.selectPath(path);

            for (XmlObject location : locations) {
                SimpleValue wsdlImport = ((SimpleValue) location);
                replaceLocation(urlToFileMap, baseUrl, wsdlImport);
            }
        }
    }

    protected abstract String[] getLocationXPathsToReplace();

    private void replaceLocation(Map<String, String> urlToFileMap, String baseUrl, SimpleValue wsdlImport)
            throws Exception {
        String location = wsdlImport.getStringValue();
        if (location != null) {
            if (location.startsWith("file:") || location.indexOf("://") > 0) {
                String newLocation = urlToFileMap.get(location);
                if (newLocation != null) {
                    wsdlImport.setStringValue(newLocation);
                } else {
                    throw new Exception("Missing local file for [" + newLocation + "]");
                }
            } else {
                String loc = Tools.joinRelativeUrl(baseUrl, location);
                String newLocation = urlToFileMap.get(loc);
                if (newLocation == null) {
                    newLocation = urlToFileMap.get(loc.replaceAll("/", "\\\\"));
                }
                if (newLocation == null) {
                    newLocation = urlToFileMap.get(loc.replaceAll("\\\\", "/"));
                }
                if (newLocation != null) {
                    wsdlImport.setStringValue(newLocation);
                } else {
                    throw new Exception("Missing local file for [" + loc + "]");
                }
            }
        }
    }

}
